package cn.monkey.family.server.domain.uc.user.role;

import cn.monkey.family.data.Result;
import cn.monkey.family.data.Results;
import cn.monkey.family.data.context.RequestContext;
import cn.monkey.family.data.uc.role.Role;
import cn.monkey.family.data.uc.role.RoleMapper;
import cn.monkey.family.data.uc.role.RoleVo;
import cn.monkey.family.data.uc.user.User;
import cn.monkey.family.data.uc.user.role.UserRole;
import cn.monkey.family.data.uc.user.role.UserRoleBindRequest;
import cn.monkey.family.data.uc.user.role.UserRoleMapper;
import cn.monkey.family.data.uc.user.role.UserRoleVo;
import cn.monkey.family.server.domain.uc.role.RoleRepository;
import cn.monkey.family.server.domain.uc.user.UserRoleBindEventListener;
import org.mapstruct.factory.Mappers;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;


public class UserRoleHandler implements UserRoleBindEventListener, UserRoleMapper {

    final RoleMapper roleMapper;
    final UserRoleRepository userRoleRepository;
    final RoleRepository roleRepository;
    final UserRoleMapper delegate;

    public UserRoleHandler(RoleMapper roleMapper,
                           UserRoleRepository userRoleRepository,
                           RoleRepository roleRepository) {
        this.roleMapper = roleMapper;
        this.userRoleRepository = userRoleRepository;
        this.roleRepository = roleRepository;
        this.delegate = Mappers.getMapper(UserRoleMapper.class);
    }


    @Override
    public UserRoleVo toVo(User user) {
        Collection<UserRole> userRoles = userRoleRepository.findByUid(user.getId());
        if (CollectionUtils.isEmpty(userRoles)) {
            return this.delegate.toVo(user);
        }
        Set<String> roleIds = userRoles.stream().map(UserRole::getRoleId)
                .collect(Collectors.toSet());
        List<Role> roles = roleRepository.findAllById(roleIds);
        if (CollectionUtils.isEmpty(roles)) {
            return this.delegate.toVo(user);
        }
        return this.mergeUserAndRoles(user, roles);
    }

    protected UserRoleVo mergeUserAndRoles(User user, Collection<Role> roles) {
        UserRoleVo userRoleVo = this.delegate.toVo(user);
        List<RoleVo> roleVos = roles.stream()
                .filter(role -> Boolean.TRUE.equals(role.getEnabled()))
                .map(roleMapper::toVo)
                .toList();
        userRoleVo.setRoles(roleVos);
        return userRoleVo;
    }

    protected List<UserRoleVo> mergeUsersAndRoles(Collection<User> users,
                                                  Collection<UserRole> userRoles,
                                                  Collection<Role> roles) {
        if (CollectionUtils.isEmpty(userRoles)) {
            return users.stream().map(this.delegate::toVo).toList();
        }
        if (CollectionUtils.isEmpty(roles)) {
            return users.stream().map(this.delegate::toVo).toList();
        }
        Map<String, RoleVo> roleVoMap = roles.stream()
                .filter(role -> Boolean.TRUE.equals(role.getEnabled()))
                .map(roleMapper::toVo)
                .collect(Collectors.toMap(RoleVo::getId, Function.identity()));
        Map<String, List<UserRole>> userRoleMap = userRoles.stream()
                .collect(Collectors.groupingBy(UserRole::getUid));
        List<UserRoleVo> userRoleVoList = users.stream().map(this.delegate::toVo).toList();
        for (UserRoleVo userRoleVo : userRoleVoList) {
            String uid = userRoleVo.getId();
            List<UserRole> userRoleList = userRoleMap.get(uid);
            if (CollectionUtils.isEmpty(userRoleList)) {
                continue;
            }
            List<RoleVo> roleVos = userRoleList.stream()
                    .map(UserRole::getRoleId)
                    .map(roleVoMap::get)
                    .filter(Objects::nonNull)
                    .toList();
            userRoleVo.setRoles(roleVos);
        }
        return userRoleVoList;
    }

    @Override
    public List<UserRoleVo> toVos(List<User> users) {
        Set<String> uidSet = users.stream().map(User::getId).collect(Collectors.toSet());
        Collection<UserRole> userRoles = userRoleRepository.findByUids(uidSet);
        if (CollectionUtils.isEmpty(userRoles)) {
            return this.mergeUsersAndRoles(users, Collections.emptyList(), Collections.emptyList());
        }
        Set<String> roleIdSet = userRoles.stream().map(UserRole::getRoleId).collect(Collectors.toSet());
        List<Role> roles = roleRepository.findAllById(roleIdSet);
        if (CollectionUtils.isEmpty(roles)) {
            return this.mergeUsersAndRoles(users, userRoles, Collections.emptyList());
        }
        return this.mergeUsersAndRoles(users, userRoles, roles);
    }

    @Override
    public Result<UserRoleVo> onUserRoleBind(RequestContext requestContext, User user, UserRoleBindRequest userRoleBindRequest) {
        Collection<String> roleIds = userRoleBindRequest.getRoleIds();
        List<Role> roles = this.roleRepository.findAllById(roleIds);
        if (CollectionUtils.isEmpty(roles)) {
            return Results.fail("ROLE_IS_EMPTY");
        }
        roles = roles.stream()
                .filter(role -> Boolean.TRUE.equals(role.getEnabled()))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(roles)) {
            return Results.fail("NO_ENABLED_ROLE");
        }
        Collection<UserRole> collection = this.toEntity(user, roles);
        this.userRoleRepository.saveAll(collection);
        return Results.ok(this.mergeUserAndRoles(user, roles));
    }
}
